package org.zoomdev.zoom.dao.entity;

import org.zoomdev.zoom.aop.Setter;
import org.zoomdev.zoom.aop.factory.GetterFactory;
import org.zoomdev.zoom.aop.factory.SetterFactory;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.adapters.GenerateKeyAdapter;
import org.zoomdev.zoom.dao.adapters.ResultSetAdapter;
import org.zoomdev.zoom.dao.adapters.parameter.ParameterAdapterRegistry;
import org.zoomdev.zoom.dao.adapters.result.ResultSetAdapters;
import org.zoomdev.zoom.dao.annotations.AutoGenerate;
import org.zoomdev.zoom.dao.annotations.ColumnIgnore;
import org.zoomdev.zoom.dao.meta.ColumnTypeMeta;
import org.zoomdev.zoom.dao.meta.TableKeyMeta;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class EntityFieldBuilder {

    private static final ConcurrentHashMap<String, MapperSetter> cache = new ConcurrentHashMap<>();

    public static Field2ColumnMappingConfig[] buildToFields(Class<?> type, Field2ColumnMappingConfig[] configs) {

        if (Map.class.isAssignableFrom(type)) {
            for (Field2ColumnMappingConfig config : configs) {
                config.setSetter(cache.computeIfAbsent(config.getColumnName(), MapperSetter::new));
                config.setResultAdapter(ResultSetAdapters.create(config.getColumnType()));
            }

        } else {
            for (Field field : Classes.getFields(type)) {
                // 内部类
                if (field.getName().startsWith("this$")) {
                    continue;
                }
                if (field.isAnnotationPresent(ColumnIgnore.class)) {
                    continue;
                }
                Field2ColumnMappingConfig config = Field2ColumnMappingConfig.find(configs, field);
                if (config == null) {
                    throw new DaoException("构建Entity失败，找不到字段" + field.getName() + "的配置");
                }
                config.setSetter(SetterFactory.FACTORY.create(field));
                config.setResultAdapter(ResultSetAdapters.create(config.getColumnType(), field.getGenericType()));

            }
        }
        return configs;
    }

    public static Field2ColumnMappingConfig[] buildToDatabases(Class<?> type,
                                                               TableKeyMeta tableMeta,
                                                               Field2ColumnMappingConfig[] configs,
                                                               Entity entity) {

        if (Map.class.isAssignableFrom(type)) {

        } else {
            GenerateKeyConfig generateKeyConfig = null;
            for (Field field : Classes.getFields(type)) {
                // 内部类
                if (field.getName().startsWith("this$")) {
                    continue;
                }
                if (field.isAnnotationPresent(ColumnIgnore.class)) {
                    continue;
                }
                Field2ColumnMappingConfig config = Field2ColumnMappingConfig.find(configs, field);
                if (config == null) {
                    throw new DaoException("构建Entity失败，找不到字段" + field.getName() + "的配置");
                }
                config.setGetter(GetterFactory.FACTORY.create(field));
                config.setParameterAdapter(ParameterAdapterRegistry.getAdapter(field, config.getColumnType()));
                if (field.isAnnotationPresent(AutoGenerate.class)) {
                    if (generateKeyConfig == null) {
                        generateKeyConfig = new GenerateKeyConfig();
                    }
                    generateKeyConfig.parse(field, config);
                }
            }
            if (generateKeyConfig != null) {
                resolveGenerateKey(entity, generateKeyConfig);
            }

            resolvePrimaryKey(entity, tableMeta, configs);

        }
        return configs;
    }

    private static void resolveGenerateKey(Entity entity, GenerateKeyConfig generateKeyConfig) {
        GenerateKeyAdapter generateKeyAdapter = generateKeyConfig.create();
        entity.setGenerateKeyAdapter(generateKeyAdapter);
        entity.setGenerateKeys(generateKeyAdapter.getGenerateKeys());
    }

    private static void resolvePrimaryKey(Entity entity, TableKeyMeta tableMeta, Field2ColumnMappingConfig[] configs) {
        ColumnTypeMeta[] pks = tableMeta.getPrimaryKeys();
        if (pks != null) {
            List<Field2ColumnMappingConfig> primaryKeys = new ArrayList<>(3);
            for (ColumnTypeMeta pk : pks) {
                primaryKeys.add(Arrays.stream(configs)
                        .filter(i -> i.getColumnName().equals(pk.getName()))
                        .findFirst().orElseThrow(ZoomException::new));
            }
            entity.setPrimaryKeys(primaryKeys.toArray(new Field2ColumnMappingConfig[0]));
        }
    }

    private static class GenerateKeyConfig {
        List<String> generateKeys = new ArrayList<>(3);
        List<ResultSetAdapter> resultSetAdapters = new ArrayList<>(3);
        List<Setter> setters = new ArrayList<>(3);

        public void parse(Field field, Field2ColumnMappingConfig config) {
            AutoGenerate autoGenerate = field.getAnnotation(AutoGenerate.class);
            String insertPlaceholder = autoGenerate.placeholder();
            if (insertPlaceholder.isEmpty()) {
                setters.add(SetterFactory.FACTORY.create(field));
                resultSetAdapters.add(ResultSetAdapters.create(Long.class, field.getGenericType()));
                generateKeys.add(config.getColumnName());
            }
            config.setInsertPlaceholder(insertPlaceholder);
        }

        public GenerateKeyAdapter create() {
            GenerateKeyAdapter generateKeyAdapter = new GenerateKeyAdapter();
            generateKeyAdapter.setGenerateKeys(generateKeys.toArray(new String[0]));
            generateKeyAdapter.setSetters(setters.toArray(new Setter[0]));
            generateKeyAdapter.setResultSetAdapters(resultSetAdapters.toArray(new ResultSetAdapter[0]));
            return generateKeyAdapter;
        }
    }

}
